package com.eddy.project.test;

import com.eddy.project.model.domain.User;
import com.eddy.project.mapper.UserMapper;
import com.eddy.project.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.concurrent.*;

/**
 * 导入用户任务
 */
@SpringBootTest
public class InsertUsers {

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserService userService;

    /**
     * 批量插入用户
     */
    public void doInsertUsers() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM = 1000;
        for (int i = 0; i < INSERT_NUM; i++) {
            User user = new User();
            user.setUserName("假eddy");
            user.setUserAccount("fakeEddy");
            user.setUserAvatar("https://eddy.plus/wp-content/uploads/2022/12/%E5%A4%A9%E5%A7%BF%E5%AE%98%E7%BD%91-%E6%96%87%E7%AB%A0%E8%83%8C%E6%99%AF%E5%9B%BE.png");
            user.setGender(0);
            user.setUserPassword("12345678");
            user.setPhone("123");
            user.setEmail("123@qq.com");
            user.setTags("[]");
            user.setUserStatus(0);
            user.setUserRole("");
            user.setPlanetCode("11111111");
            userMapper.insert(user);
        }
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis());
    }

    /**
     * 线程池的定义
     * 1，IO 密集型：分配的核心线程数可以大于 CPU 核数
     * 这类程序的主要特点是大部分时间都花在等待IO操作上，例如文件读写、网络通信等。
     * 理想情况下，线程池的大小可以设置得比CPU核心数多一些，以便当一个线程在等待IO时，其他线程仍然可以执行任务。
     * 2， CPU 密集型：分配的核心线程数 = CPU - 1
     * 这类程序的主要特点是需要大量的CPU计算资源，计算密集型任务通常会占用大量的CPU时间。-- 计算
     * 在这种情况下，线程池的大小应该设置为不超过CPU核心数，以避免因为线程过多而导致线程切换开销过大。
     */

    /**
     * IO 密集型
     */
//    public static final ExecutorService IO_EXECUTOR = new ThreadPoolExecutor(
//            2,
//            128,
//            15,
//            TimeUnit.SECONDS,
//            new SynchronousQueue<>()
//    );

    /**
     * CPU 密集型
     */
    public static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//    public static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//    private static final int CPU_CORE_POOL_SIZE = Math.max(Math.min(MAXIMUM_POOL_SIZE, 4), Math.min(CPU_COUNT + 1, 9));
//    public static final ExecutorService executorService = new ThreadPoolExecutor(
//            CPU_CORE_POOL_SIZE,
//            CPU_COUNT * 2 + 1,
//            30,
//            TimeUnit.SECONDS,
//            new LinkedBlockingQueue<>(256)
//    );

    public static final ExecutorService executorService = new ThreadPoolExecutor(
            //corePoolSize 核心线程数
            20,
            // maximumPoolSize 线程总数=核心/非核心线程总数
            40,
            // 默认情况下指的是非核心线程的空闲时间
            // 如果设置(线程池.allowCoreThreadTimeOut=true)代码如下：核心线程/非核心线程允许的空闲时间
            10L,
            //unit：keepAliveTime 的单位 10s
            TimeUnit.SECONDS,
            //workQueue：存储等待执行的任务
            new LinkedBlockingQueue<>(100),
            //defaultThreadFactory 默认线程工厂：创建的线程有相同的优先级，非守护线程，有线程名称
            //privilegedThreadFactory 继承了 defaultThreadFactory 是运行在这个线程的任务拥有和这个线程相同的访问控制和类加载器(ClassLoader)
            //Executors.privilegedThreadFactory(),
            Executors.defaultThreadFactory(),
            //拒绝策略，：  如果 workQueue 队列满了，当前的线程池没有空闲的线程，就会触发拒绝策略
            /**
             * 拒绝任务的策略
             * AbortPolicy (默认) :抛异常
             * CallerRusPolicy :用调用者所在的线程执行任务
             * DiscardOldestPolicy :丢弃队列中最靠前的任务
             * DiscardPolicy :丢弃当前任务
             */
            new ThreadPoolExecutor.AbortPolicy()
    );


    /**
     * 并发批量插入用户
     */
    @Test
    public void doConcurrencyInsertUsers() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM = 100000; //  5000 * 20  = 100000
        int batchSize = 5000;
        int j = 0;
        ArrayList<CompletableFuture<Void>> futureArrayList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            ArrayList<User> users = new ArrayList<>();
            while (true) {
                j++;
                User user = new User();
                user.setUserName("假eddy");
                user.setUserAccount("fakeEddy");
                user.setUserAvatar("https://eddy.plus/wp-content/uploads/2022/12/%E5%A4%A9%E5%A7%BF%E5%AE%98%E7%BD%91-%E6%96%87%E7%AB%A0%E8%83%8C%E6%99%AF%E5%9B%BE.png");
                user.setGender(0);
                user.setUserPassword("12345678");
                user.setPhone("123");
                user.setEmail("123@qq.com");
                user.setTags("[]");
                user.setUserStatus(0);
                user.setUserRole("");
                user.setPlanetCode("11111111");
                users.add(user);
                if ((j % batchSize) == 0) {
                    break;
                }
            }

            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                System.out.println("threadName:" + Thread.currentThread().getName());
                userService.saveBatch(users, batchSize);
            },executorService);
            futureArrayList.add(future);
        }
        CompletableFuture.allOf(futureArrayList.toArray(new CompletableFuture[]{})).join();
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis());
    }

    public void stopWatchTest() throws InterruptedException {
        StopWatch stopWatch = new StopWatch("用户注册");

        //启动任务一
        stopWatch.start("保存用户信息");
        //执行业务逻辑
        TimeUnit.SECONDS.sleep(1);
        stopWatch.stop();

        //启动任务二
        stopWatch.start("创建用户钱包信息");
        //执行业务逻辑
        TimeUnit.SECONDS.sleep(2);
        stopWatch.stop();
        //会输出所有任务的信息
        System.out.println(stopWatch.prettyPrint());
        // 只输出总的：StopWatch '用户注册': running time = 3004621914 ns
        //System.out.println(stopWatch.shortSummary());

        // 任务总的耗时  如果你想获取到每个任务详情（包括它的任务名、耗时等等）可使用
        System.out.println("所有任务总耗时（毫秒）：" + stopWatch.getTotalTimeMillis());
        System.out.println("任务总数：" + stopWatch.getTaskCount());
        System.out.println("所有任务详情：" + stopWatch.getTaskInfo()); // 拿到所有的任务
    }
}
